#include "frame_src_file.h"
#include <chrono>
#ifdef __cplusplus
extern "C"
{
#endif
#include "mov-reader.h"
#include "mov-format.h"
#include "mpeg4-avc.h"
#include "mpeg4-aac.h"
#include "mpeg4-hevc.h"
#include "mov-file-buffer.h"
#include <assert.h>
#ifdef __cplusplus
}
#endif
#ifndef _WIN32
#include <unistd.h>
#include <sys/stat.h>
#endif
#include <string.h>
#include"frame_src.h"

extern "C" const struct mov_buffer_t* mov_file_buffer(void);

struct mov_h265_test_t
{
	struct mpeg4_hevc_t hevc;

	int track;
	int width;
	int height;
	uint32_t pts, dts;
	const uint8_t* ptr;

	int vcl;
};

static uint8_t s_packet[2 * 1024 * 1024];
//static uint8_t s_buffer[4 * 1024 * 1024];
static FILE* s_vfp, * s_afp;
static struct mpeg4_hevc_t s_hevc;
static struct mpeg4_avc_t s_avc;
static struct mpeg4_aac_t s_aac;
//static struct webm_vpx_t s_vpx;
//static struct opus_head_t s_opus;
//static struct aom_av1_t s_av1;
static uint32_t s_aac_track = 0xFFFFFFFF;
static uint32_t s_avc_track = 0xFFFFFFFF;
static uint32_t s_av1_track = 0xFFFFFFFF;
static uint32_t s_vpx_track = 0xFFFFFFFF;
static uint32_t s_hevc_track = 0xFFFFFFFF;
static uint32_t s_opus_track = 0xFFFFFFFF;
static uint32_t s_mp3_track = 0xFFFFFFFF;
static uint32_t s_subtitle_track = 0xFFFFFFFF;

extern CFrmSrc* g_rec_buf;

inline const char* ftimestamp(uint32_t t, char* buf)
{
	sprintf(buf, "%02u:%02u:%02u.%03u", t / 3600000, (t / 60000) % 60, (t / 1000) % 60, t % 1000);
	return buf;
}

static int  onread(void* flv, uint32_t track, const void* buffer, size_t bytes, int64_t pts, int64_t dts, int flags,int * is_key_flag)
{
	static char s_pts[64], s_dts[64];
	static int64_t v_pts, v_dts;
	static int64_t a_pts, a_dts;
	int nRet = 0;
	if (s_avc_track == track)
	{
		v_pts = pts;
		v_dts = dts;

		nRet = h264_mp4toannexb(&s_avc, buffer, bytes, s_packet, sizeof(s_packet), is_key_flag);
		if (is_key_flag)
		{
		//	printf("[H264] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u%s\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - v_pts), (int)(dts - v_dts), (unsigned int)bytes, flags ? " [I]" : "");
		}
	//	fwrite(s_packet, 1, nRet, s_vfp);
	}
	else if (s_hevc_track == track)
	{
		uint8_t nalu_type = (((const uint8_t*)buffer)[3] >> 1) & 0x3F;
		uint8_t irap = 16 <= nalu_type && nalu_type <= 23;

	//	printf("[H265] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u%s,%d\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - v_pts), (int)(dts - v_dts), (unsigned int)bytes, flags ? " [I]" : "", (unsigned int)nalu_type);
		v_pts = pts;
		v_dts = dts;

		nRet = h265_mp4toannexb(&s_hevc, buffer, bytes, s_packet, sizeof(s_packet), is_key_flag);
		//fwrite(s_packet, 1, nRet, s_vfp);
	}
	else if (s_av1_track == track)
	{
		printf("[AV1] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u%s\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - v_pts), (int)(dts - v_dts), (unsigned int)bytes, flags ? " [I]" : "");
		v_pts = pts;
		v_dts = dts;

		//int n = aom_av1_codec_configuration_record_save(&s_av1, s_packet, sizeof(s_packet));
		//fwrite(s_packet, 1, n, s_vfp);
	}
#if 0
	else if (s_vpx_track == track)
	{
		printf("[VP9] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u%s\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - v_pts), (int)(dts - v_dts), (unsigned int)bytes, flags ? " [I]" : "");
		v_pts = pts;
		v_dts = dts;

		//int n = aom_av1_codec_configuration_record_save(&s_av1, s_packet, sizeof(s_packet));
		//fwrite(s_packet, 1, n, s_vfp);
	}
#endif
	else if (s_aac_track == track)
	{
		printf("[AAC] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - a_pts), (int)(dts - a_dts), (unsigned int)bytes);
		a_pts = pts;
		a_dts = dts;

		//uint8_t adts[32];
		//int n = mpeg4_aac_adts_save(&s_aac, bytes, adts, sizeof(adts));
		//fwrite(adts, 1, n, s_afp);
		//fwrite(buffer, 1, bytes, s_afp);
	}
#if 0
	else if (s_opus_track == track)
	{
		printf("[OPUS] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - a_pts), (int)(dts - a_dts), (unsigned int)bytes);
		a_pts = pts;
		a_dts = dts;
	}
#endif
	else if (s_subtitle_track == track)
	{
		static int64_t t_pts, t_dts;
		printf("[TEXT] pts: %s, dts: %s, diff: %03d/%03d, bytes: %u, text: %.*s\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - t_pts), (int)(dts - t_dts), (unsigned int)bytes, (int)bytes - 2, (const char*)buffer + 2);
		t_pts = pts;
		t_dts = dts;
	}
	else
	{
	//	printf("%d\n", track);
		//assert(0);
	}
	return nRet;
}

static void mov_video_info(void* /*param*/, uint32_t track, uint8_t object, int /*width*/, int /*height*/, const void* extra, size_t bytes)
{
	if (MOV_OBJECT_H264 == object)
	{
		s_vfp = fopen("f:\\v.h264", "wb");
		s_avc_track = track;
		mpeg4_avc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s_avc);
	}
	else if (MOV_OBJECT_HEVC == object)
	{
		s_vfp = fopen("f:\\v.h265", "wb");
		s_hevc_track = track;
		mpeg4_hevc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s_hevc);
	}
#if 0
	else if (MOV_OBJECT_AV1 == object)
	{
		s_vfp = fopen("v.obus", "wb");
		s_av1_track = track;
		aom_av1_codec_configuration_record_load((const uint8_t*)extra, bytes, &s_av1);
	}
	else if (MOV_OBJECT_VP9 == object)
	{
		s_vfp = fopen("v.vp9", "wb");
		s_vpx_track = track;
		webm_vpx_codec_configuration_record_load((const uint8_t*)extra, bytes, &s_vpx);
	}
#endif
	else
	{
		assert(0);
	}
}


static void mov_subtitle_info(void* /*param*/, uint32_t track, uint8_t object, const void* /*extra*/, size_t /*bytes*/)
{
	s_subtitle_track = track;
}

CFrame_src_file::CFrame_src_file()
{
    m_ch = 0;

	m_b_run  = false;
    m_fbox_fd = NULL;
}

CFrame_src_file::~CFrame_src_file()
{

}

int  CFrame_src_file::Open(const char* filePath, int ch)
{
	int ret = 0;
	if (strstr(filePath, "mp4"))
	{
		ret = OpenMp4(filePath,ch);
	}
	else  if (strstr(filePath, "h264"))
	{
		ret = OpenH264(filePath, ch);
	}
	else if (strstr(filePath, "hevc")||
		strstr(filePath, "h265"))
	{
		ret = OpenH265(filePath, ch);
	}
	return ret;
}

int  CFrame_src_file::OpenMp4(const char* filePath, int ch)
{
	m_fp = fopen(filePath, "rb");
	if (m_fp == NULL)
		return -1;
	m_fbox_fd = mov_reader_create(mov_file_buffer(), m_fp);
	uint64_t duration = mov_reader_getduration(m_fbox_fd);

	struct mov_reader_trackinfo_t info = { mov_video_info, NULL, mov_subtitle_info };
	mov_reader_getinfo(m_fbox_fd, &info, NULL);
	
	m_ch = ch;
	m_file_type = FILE_TYPE_MP4;
	return 0;
}

int  CFrame_src_file::OpenH264(const char* filePath, int ch)
{
	m_fp = fopen(filePath, "rb");
	if (m_fp == NULL)
		return -1;

	struct stat info;
	stat(filePath, &info);
	int size = info.st_size;
	printf("file path %s  size %d.\n ", filePath,size);

	m_buf_h26x = new unsigned char[size];
	m_buf_size = size;
	int ret = fread(m_buf_h26x,size,1,m_fp);

	if (ret != 1)
	{
		printf("read error %d .\n ",errno);
	}

	m_ch = ch;
	m_file_type = FILE_TYPE_h264;
	return 0;
}

int  CFrame_src_file::OpenH265(const char* filePath, int ch)
{
	m_fp = fopen(filePath, "rb");
	if (m_fp == NULL)
		return -1;

	struct stat info;
	stat(filePath, &info);
	int size = info.st_size;
	printf("file path %s  size %d.\n ", filePath,size);

	m_buf_h26x = new unsigned char[size];
	m_buf_size = size;
	int ret = fread(m_buf_h26x,size,1,m_fp);

	if (ret != 1)
	{
		printf("read error %d .\n ",errno);
	}

	m_ch = ch;
	m_file_type = FILE_TYPE_h265;
	return 0;
}

void CFrame_src_file::Close()
{
 
	mov_reader_destroy((mov_reader_t*)m_fbox_fd);
}
static const uint8_t* h26x_startcode(const uint8_t* data, int bytes)
{
	int i;
	for (i = 2; i + 1 < bytes; i++)
	{
		if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2])
		{
			if (data[i + 1] == 0)
			{
				continue;
			}
			return data + i + 1;
		}
	}

	return NULL;
}


int  CFrame_src_file::ReadFrameh264(unsigned char* frameBuf, int bufSize, int& frmType)
{
	int len = 0;

	uint8_t* pData = (uint8_t*)(m_buf_h26x + m_buf_pos);
	int pos = 0;
	int frm_len = 0;
	int data_pos = 0;
	ENC_NaluUnit nalu;
	int bytes = m_buf_size - m_buf_pos;
	
	const uint8_t* p_start, * next, * end;
	int n;

	end = (const uint8_t*)pData + bytes;
	//p = h264_startcode((const uint8_t*)h264, size);
	p_start = h26x_startcode((uint8_t*)(pData -4), bytes);
	 
	n = p_start - pData;
	if (n !=0 )
	{
		m_buf_pos += n;
	}
	int nalu_type = 0; // buffer[i] & 0x1f;
	int nalu_size = 0;
	while (p_start)
	{
		nalu_type = *((char*)p_start) & 0x1f;
		next = h26x_startcode(p_start, (int)(end - p_start));
		if (next)
		{
			n = next - p_start - 3;
		}
		else
		{
			n = end - p_start;
		}
		nalu_size = n + 4;
		if (nalu_type == 0x06 ||
			nalu_type == 0x07 ||
			nalu_type == 0x08)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			data_pos += nalu_size;
			frm_len += nalu_size;
			p_start = next;
			continue;
		}
		if (nalu_type == 0x05)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			frm_len += nalu_size;
			frmType = IFrame;
			p_start = next;
			break;
		}
		if (nalu_type == 0x01)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			frm_len += nalu_size;
			frmType = PFrame;
			p_start = next;
			break;
		}
		p_start = next;
	}

	m_buf_pos = p_start - m_buf_h26x;


	if (m_buf_pos > m_buf_size)
	{
		m_buf_pos = 0;
	}

	if (m_buf_pos + 4 > m_buf_size)
	{
		m_buf_pos = 0;
	}

	return frm_len;
}
#if 0
int  CFrame_src_file::ReadFrameh265(unsigned char* frameBuf, int bufSize, int& frmType)
{
	ptrdiff_t n;
	const uint8_t* p;
	const uint8_t* next = m_buf_h26x + m_buf_pos;
	const uint8_t *end = NULL;
	struct mov_h265_test_t* ctx;
	const uint8_t* start_buf = m_buf_h26x + m_buf_pos;// nalu - 3;
	uint8_t nalutype = 0; //(nalu[0] >> 1) & 0x3f;
	int vcl = 0;

	end = (const uint8_t*)m_buf_h26x + m_buf_size;
	p = h26x_startcode((const uint8_t*)start_buf, m_buf_size);
	int frm_sz = 0;
	while (p)
	{
		next = h26x_startcode(p, (int)(end - p));
		if (next)
		{
			n = next - p - 3;
		}
		else
		{
			n = end - p;
		}

		while (n > 0 && 0 == p[n - 1]) n--; // filter tailing zero

		assert(n > 0);
		if (n > 0)
		{
			nalutype = (p[0] >> 1) & 0x3f;
			if (nalutype == 19 ||
				nalutype == 20
				)
			{
				frmType = IFrame;
			}
			if (m_vcl > 0 && h265_is_new_access_unit((const uint8_t*)p, n))
			{
				//int r = h265_write(ctx, ctx->ptr, frm_sz);
				//if (-1 == r)
				//	return; // wait for more data
				frm_sz = p - start_buf;

				memcpy(frameBuf, start_buf, frm_sz);
				m_vcl = 0;
				break;
			}

			if (nalutype <= 31)
				++m_vcl;
		}

		p = next;
	}

	m_buf_pos = next - m_buf_h26x;


	if (m_buf_pos > m_buf_size)
	{
		m_buf_pos = 0;
	}

	if (m_buf_pos + 4 > m_buf_size)
	{
		m_buf_pos = 0;
	}

	return frm_sz;
}
#else
int  CFrame_src_file::ReadFrameh265(unsigned char* frameBuf, int bufSize, int& frmType)
{
	int len = 0;
	uint8_t* pData = (uint8_t*)(m_buf_h26x + m_buf_pos);
	int pos = 0;
	int frm_len = 0;
	int data_pos = 0;
	ENC_NaluUnit nalu;
	int bytes = m_buf_size - m_buf_pos;
	
	const uint8_t* p_start, * next, * end;
	int n;

	end = (const uint8_t*)pData + bytes;
	//p = h264_startcode((const uint8_t*)h264, size);
	p_start = h26x_startcode((uint8_t*)(pData -4), bytes);
	
	n = p_start - pData;
	if (n !=0 )
	{
		m_buf_pos += n;
	}
	int nalu_type = 0; // buffer[i] & 0x1f;
	int nalu_size = 0;
	while (p_start)
	{
		nalu_type = (*((uint16_t*)p_start) & 0x7e) >> 1;

		next = h26x_startcode(p_start, (int)(end - p_start));
		if (next)
		{
			n = next - p_start - 3;
		}
		else
		{
			n = end - p_start;
		}
		nalu_size = n + 4;
		if(  nalu_type==32 ||
			nalu_type == 33 ||
			nalu_type == 34 ||
			nalu_type == 35 ||
			nalu_type == 38 ||
			nalu_type == 39
			)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			data_pos += nalu_size;
			frm_len += nalu_size;
			p_start = next;
			continue;
		}
		if (nalu_type != 19 &&
			nalu_type != 20 &&
			nalu_type != 1
			)
		{
			printf("other nalu %d .\n", nalu_type);
		}
		if (nalu_type == 19 || 
			nalu_type == 20
			)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			frm_len += nalu_size;
			frmType = IFrame;
			p_start = next;
			break;
		}
		if (nalu_type == 1)
		{
			memcpy(frameBuf + data_pos, p_start - 4, nalu_size);
			frm_len += nalu_size;
			frmType = PFrame;
			p_start = next;
			break;
		}

		p_start = next;
	}

	m_buf_pos = p_start - m_buf_h26x;


	if (m_buf_pos > m_buf_size)
	{
		m_buf_pos = 0;
	}

	if (m_buf_pos + 4 > m_buf_size)
	{
		m_buf_pos = 0;
	}

	return frm_len;
}
#endif
int CFrame_src_file::ReadSimFrm(unsigned char* frameBuf, int bufSize, int& frmType)
{
    int  date_size = 0;
	int  key_flag = 0;

    date_size = mov_reader_read((struct mov_reader_t*)m_fbox_fd, frameBuf, bufSize, onread, NULL, &key_flag);

	if (key_flag)
	{
		frmType = IFrame;
	}
	else
	{
		frmType = PFrame;
	}

    return date_size;
}

int CFrame_src_file::start()
{
    printf(" CFrame_src_file::Start .\n");
    if (m_pkting)
    {
        return -1;
    }
	m_b_run  = true;
    m_recThd = thread(&CFrame_src_file::CreateSimulateData, this);
    m_recThd.detach();
    return 0;
}

void CFrame_src_file::stop()
{
    m_b_run = false;
    return ;
}

// static member function for access static member variable
int  CFrame_src_file::CreateSimulateData()
{
    int ret = 0;

    int sleepMS = 1000 / m_rate - 1;
    int frmCnt = 0;
	int buf_sz = 2 << 20;
    unsigned char* pBuf = new  unsigned char[buf_sz];
    int       frmSize = 0;
    int       frameType = PFrame;

    m_pkting.store(true);
	int64_t  timestamp = 0;
	CFrmSrc * pfrm_arr = g_rec_buf;
	unsigned char* pdata = s_packet;
	if (m_file_type == FILE_TYPE_h264 || 
		m_file_type == FILE_TYPE_h265
		)
	{
		pdata = pBuf;
	}
	else
	{
		 pdata = s_packet;
	}
	int codec = 0;
	m_b_run = true;
    while (m_b_run)
    {
		if (m_file_type == FILE_TYPE_h264)
		{
			frameType = PFrame;
			frmSize = ret = ReadFrameh264(pBuf, buf_sz, frameType);
			codec = 0;
		}
		else if (m_file_type == FILE_TYPE_h265)
		{
			frameType = PFrame;
			codec = 1;
			frmSize = ret = ReadFrameh265(pBuf, buf_sz, frameType);
		}
		else
		{
			frmSize = ret = ReadSimFrm(pBuf, buf_sz, frameType);
		}

        if (ret < 0)
        {
            this_thread::sleep_for(chrono::milliseconds(30));

			if (m_file_type == FILE_TYPE_MP4)
			{
				mov_reader_seek(m_fbox_fd, &timestamp);
			}

            continue;
        }
		if (ret == 0)
		{
			continue;
		}

       ret = pfrm_arr->WriteOneFrame(pdata, frmSize, frameType, m_ch,0, codec);
	   if (m_b_show &&frameType == IFrame)
	   {
		   printf("CreateSimulateData WriteOneFrame size %d m_ch %d ret %d .\n", frmSize, m_ch,ret );
	   }
#ifdef _WIN32
	   Sleep(sleepMS);
#else
	   usleep(33*1000);
#endif
//       this_thread::sleep_for(chrono::milliseconds(30));
    }
	m_b_run = false;
    m_pkting.store(false);

    return 0;
}
int mpeg_h264_find_nalu(const uint8_t* p, size_t bytes, size_t& leading)
{
	size_t i, zeros;
	for (zeros = i = 0; i + 1 < bytes; i++)
	{
		if (0x01 == p[i] && zeros >= 2)
		{
			assert(i >= zeros);
			leading = (zeros > 2 ? 3 : zeros) + 1; // zeros + 0x01
			return (int)(i + 1);
		}

		zeros = 0x00 != p[i] ? 0 : (zeros + 1);
	}

	return -1;
}
/*

int CFrame_src_file::ReadOneNaluFromBuf(const unsigned char* buffer, unsigned int nBufferSize, unsigned int offSet, ENC_NaluUnit& nalu)
{
	unsigned int i = offSet;
	
	size_t  leading = 3;
	unsigned int start_pos = 0;
	unsigned int  end_pos = 0;
	while (i < nBufferSize)
	{
		start_pos = mpeg_h264_find_nalu(buffer + i, nBufferSize - i, leading);
		if (start_pos == -1)
		{
			return -1;
		}
		end_pos = mpeg_h264_find_nalu(buffer + i, nBufferSize - i, leading);
		if (end_pos == -1)
		{
			return -1;
		}
		nalu.size = end_pos - start_pos;
		nalu.type = buffer[start_pos] & 0x1f;
		nalu.data = (unsigned char*)&buffer[i] - leading;
		return (nalu.size + i - offSet);
	}

	return 0;
}
*/
int CFrame_src_file::ReadOneNaluFromBuf(const unsigned char* buffer, unsigned int nBufferSize, unsigned int offSet, ENC_NaluUnit& nalu)
{
	unsigned int i = offSet;
	unsigned int pos = 0;

	while (i < nBufferSize)
	{
		if (buffer[i++] == 0x00 &&
			buffer[i++] == 0x00 &&
			buffer[i++] == 0x00 &&
			buffer[i++] == 0x01
			)
		{
			pos = i;
			while (pos < nBufferSize)
			{
				if (buffer[pos++] == 0x00 &&
					buffer[pos++] == 0x00 &&
					buffer[pos++] == 0x00 &&
					buffer[pos++] == 0x01
					)
				{
					break;
				}
			}

			nalu.size = pos  - i;
			
			nalu.type = buffer[i] & 0x1f;
			nalu.data = (unsigned char*)&buffer[i] - 4;
			return (nalu.size + i - offSet);
		}
	}

	return 0;
}
